/*
 * Unidata Platform Community Edition
 * Copyright (c) 2013-2020, UNIDATA LLC, All rights reserved.
 * This file is part of the Unidata Platform Community Edition software.
 *
 * Unidata Platform Community Edition is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Unidata Platform Community Edition is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 */

package org.unidata.mdm.data.service.segments.records.delete;

import java.util.Date;
import java.util.Objects;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.unidata.mdm.core.service.MetaModelService;
import org.unidata.mdm.core.type.model.EntityElement;
import org.unidata.mdm.core.type.timeline.Timeline;
import org.unidata.mdm.data.context.DeleteRequestContext;
import org.unidata.mdm.data.context.GetRecordTimelineRequestContext;
import org.unidata.mdm.data.context.RecordIdentityContextSupport;
import org.unidata.mdm.data.dto.DeleteRecordDTO;
import org.unidata.mdm.data.exception.DataExceptionIds;
import org.unidata.mdm.data.exception.DataProcessingException;
import org.unidata.mdm.data.module.DataModule;
import org.unidata.mdm.data.service.impl.CommonRecordsComponent;
import org.unidata.mdm.data.service.segments.AttributesAutogenerationSupport;
import org.unidata.mdm.data.service.segments.ExternalIdAutogenerationSupport;
import org.unidata.mdm.data.type.apply.RecordDeleteChangeSet;
import org.unidata.mdm.data.type.data.OriginRecord;
import org.unidata.mdm.data.type.keys.RecordKeys;
import org.unidata.mdm.meta.configuration.Descriptors;
import org.unidata.mdm.system.type.pipeline.Start;

/**
 * @author Mikhail Mikhailov
 *         'Delete' pre-check validator.
 */
@Component(RecordDeleteStartExecutor.SEGMENT_ID)
public class RecordDeleteStartExecutor extends Start<DeleteRequestContext, DeleteRecordDTO>
    implements
        RecordIdentityContextSupport,
        AttributesAutogenerationSupport,
        ExternalIdAutogenerationSupport {
    /**
     * Logger.
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(RecordDeleteStartExecutor.class);
    /**
     * This segment ID.
     */
    public static final String SEGMENT_ID = DataModule.MODULE_ID + "[RECORD_DELETE_START]";
    /**
     * Localized message code.
     */
    public static final String SEGMENT_DESCRIPTION = DataModule.MODULE_ID + ".record.delete.start.description";
    /**
     * Common component.
     */
    @Autowired
    private CommonRecordsComponent commonRecordsComponent;
    /**
     * Meta model service.
     */
    @Autowired
    private MetaModelService metaModelService;
    /**
     * Constructor.
     */
    public RecordDeleteStartExecutor() {
        super(SEGMENT_ID, SEGMENT_DESCRIPTION, DeleteRequestContext.class, DeleteRecordDTO.class);
    }
    /**
     * Execute.
     */
    @Override
    public void start(DeleteRequestContext ctx) {
        setup(ctx);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public String subject(DeleteRequestContext ctx) {
        setup(ctx);
        RecordKeys keys = ctx.keys();
        return keys.getEntityName();
    }

    protected void setup(DeleteRequestContext ctx) {

        if (ctx.setUp()) {
            return;
        }

        setupAutogeneration(ctx);

        setupKeysAndTimeline(ctx);

        setupCheck(ctx);

        setupFields(ctx);

        ctx.setUp(true);
    }

    protected void setupCheck(DeleteRequestContext ctx) {

        RecordKeys keys = ctx.keys();

        checkRecordPresence(ctx, keys);

        checkFlagsDefined(ctx);

        checkMultipleFlags(ctx);

        checkDraftFlags(ctx);

        checkRecordState(ctx, keys);
    }

    protected void setupKeysAndTimeline(DeleteRequestContext ctx) {

        Timeline<OriginRecord> timeline = ctx.currentTimeline();
        RecordKeys keys = null;
        if (timeline == null) {
            timeline = commonRecordsComponent.loadTimeline(
                    GetRecordTimelineRequestContext.builder(ctx)
                            .fetchData(true)
                            .draftId(ctx.getDraftId())
                            .parentDraftId(ctx.getParentDraftId())
                            .build());

            keys = Objects.nonNull(timeline) ? timeline.getKeys() : null;
        } else {
            keys = timeline.getKeys();
        }

        ctx.currentTimeline(timeline);
        ctx.keys(keys);
    }

    protected void setupAutogeneration(DeleteRequestContext ctx) {

        // 0. Stop on valid keys
        if (ctx.isValidRecordKey()) {
            return;
        }

        // 1. Select model element
        String entityName = selectEntityName(ctx);
        if (Objects.isNull(entityName)) {
            return;
        }

        EntityElement info = metaModelService.instance(Descriptors.DATA).getElement(entityName);

        // 2. Check code attributes generation
        setupAttributesAutogeneration(info, ctx);

        // 3. Check for "ext id autogeneration" rules turned on and apply them
        setupExternalIdAutogeneration(info, ctx);
    }

    protected void setupFields(DeleteRequestContext ctx) {

        if (Objects.isNull(ctx.changeSet())) {
            ctx.changeSet(new RecordDeleteChangeSet());
        }

        ctx.timestamp(new Date());
    }

    private void checkRecordPresence(DeleteRequestContext ctx, RecordKeys keys) {

        if (keys == null) {
            final String message = "Record submitted for {} deletion cannot be identified by supplied keys - "
                    + "etalon id: [{}], origin id [{}], external id [{}], source system [{}], name [{}]";
            LOGGER.warn(message,
                    ctx.isInactivateEtalon() ? "(soft) " : StringUtils.EMPTY,
                    ctx.getEtalonKey(),
                    ctx.getOriginKey(),
                    ctx.getExternalId(),
                    ctx.getSourceSystem(),
                    ctx.getEntityName());
            throw new DataProcessingException(message, DataExceptionIds.EX_DATA_INVALID_DELETE_INPUT,
                    ctx.isInactivateEtalon() ? "(soft) " : StringUtils.EMPTY,
                    ctx.getEtalonKey(),
                    ctx.getOriginKey(),
                    ctx.getExternalId(),
                    ctx.getSourceSystem(),
                    ctx.getEntityName());
        }
    }
    private void checkFlagsDefined(DeleteRequestContext ctx) {

        if (!ctx.isInactivateEtalon()
         && !ctx.isInactivateOrigin()
         && !ctx.isInactivatePeriod()
         && !ctx.isWipe()) {
            throw new DataProcessingException("No action was given to DELETE. "
                   + "One of 'inactivateEtalon', 'inactivateOrigin', 'inactivatePeriod' or 'wipe' must be defined.",
                   DataExceptionIds.EX_DATA_DELETE_ACTION_NOT_DEFINED);
        }
    }
    private void checkMultipleFlags(DeleteRequestContext ctx) {

        if ((ctx.isWipe() && (ctx.isInactivateEtalon() || ctx.isInactivatePeriod() || ctx.isInactivateOrigin()))
         || (ctx.isInactivateEtalon() && (ctx.isWipe() || ctx.isInactivatePeriod() || ctx.isInactivateOrigin()))
         || (ctx.isInactivatePeriod() && (ctx.isInactivateEtalon() || ctx.isWipe() || ctx.isInactivateOrigin()))
         || (ctx.isInactivateOrigin() && (ctx.isInactivateEtalon() || ctx.isInactivatePeriod() || ctx.isWipe()))) {
           throw new DataProcessingException("More than one action was given to DELETE. "
                   + "Only one of 'inactivateEtalon', 'inactivateOrigin', 'inactivatePeriod' or 'wipe' must be defined.",
                   DataExceptionIds.EX_DATA_DELETE_MORE_THAN_ONE_ACTION_DEFINED);
        }
    }
    private void checkDraftFlags(DeleteRequestContext ctx) {

        if (ctx.isDraftOperation() && ctx.isWipe()) {
            throw new DataProcessingException("Invalid input combination - wipe operation cannot be handled as draft.",
                    DataExceptionIds.EX_DATA_DELETE_WIPE_AND_DRAFT_COMBINATION);
        }
    }
    private void checkRecordState(DeleteRequestContext ctx, RecordKeys keys) {

        if (ctx.isInactivateEtalon() && !keys.isActive()) {
            final String message = "Record [{}], etalon id [{}] is already in inactive state.";
            LOGGER.warn(message, keys.getEntityName(), keys.getEtalonKey().getId());
            throw new DataProcessingException(message, DataExceptionIds.EX_DATA_RECORDS_DELETE_ETALON_ALREADY_INACTIVE,
                    keys.getEntityName(), keys.getEtalonKey().getId());
        }
    }
}
